package net.atomarrow.db.parser;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.atomarrow.bean.Pager;
import net.atomarrow.configs.DbConfig;
import net.atomarrow.db.orm.Attribute;
import net.atomarrow.db.orm.OrmContext;
import net.atomarrow.db.parser.batch.BatchSetter;
import net.atomarrow.db.parser.batch.BatchSqlHolder;
import net.atomarrow.domains.Domain;
import net.atomarrow.util.BeanUtil;
import net.atomarrow.util.HtmlUtil;
import net.atomarrow.util.StringLink;



/**
 * jdbc语句解析类
 * @author Michael
 *
 */
public class JdbcParser extends BaseParser implements Parser{

	private static Parser INSTANCE;
	public static Parser getInstance(){
		if(INSTANCE==null){
			INSTANCE=new JdbcParser();
		}
		return INSTANCE;
	}


	@Override
	public String getAddSql(String tableName, Domain domain) {
		List<Attribute> attributes=new ArrayList<Attribute>(OrmContext.getAttributes(tableName));
		if(attributes==null){
			throw new RuntimeException("orm config not find");
		}
		List<Serializable> values = new ArrayList<Serializable>();
		Attribute at;
		for(int i=0;i<attributes.size();i++){
		    at=attributes.get(i);
		    if(at.isDbColumn()==false){
		        attributes.remove(i);
		        i--;
		        continue;
		    }
		    if(at.isDbColumn()){
		        Serializable value=(Serializable) BeanUtil.getSimpleProperty(domain,at.getName());
		        if(value==null&&!domain.isNeedUpdate(at.getName())){
		            attributes.remove(i);
	                i--;
	                continue;
		        }
		        values.add(value);
            }
		}
		return generateAddSql(tableName,attributes,values);
	}

	private String generateAddSql(String tableName, List<Attribute> attributes, List<Serializable> values) {
		StringBuilder sb = new StringBuilder();
		sb.append("insert into ");
		sb.append(tableName);
		sb.append("(");
		for(Attribute at:attributes){
			if(!at.isDbColumn()){
				continue;
			}
			sb.append("`");
			sb.append(at.getName());
			sb.append("`,");
		}
		sb.deleteCharAt(sb.length()-1);
		sb.append(") values(");
		for(int i=0;i<values.size();i++){
		    Serializable value = processValue(values.get(i));
		    if(value instanceof String){
                value=HtmlUtil.processScript((String)value);
            }
			sb.append(value);
			sb.append(",");
		}
		sb.deleteCharAt(sb.length()-1);
		sb.append(")");
		return sb.toString();
	}



	@Override
	public String getSelectHql(Conditions conditions) {
		if(conditions.sql!=null){
		    return conditions.sql;
		}else if(conditions.getFinalSelectValue()!=null){
		    conditions.link = null;
		    if (conditions.cs.size() == 0) {
		        return conditions.getFinalSelectValue();
	        }
		    return conditions.getFinalSelectValue()+parse(conditions,new StringBuilder(" where "));
		}
		return conditions.getSelectValue()+super.getSelectHql(conditions);
	}
	@Override
	public String getUpdateSql(String tableName, Domain domain) {
		domain.checkChange();
        StringBuilder sb = new StringBuilder();
        sb.append("update ");
        sb.append(tableName);
        sb.append(" set ");
        List<Attribute> attributes = OrmContext.getAttributes(tableName);
        for (Attribute at : attributes) {
            if (!at.isDbColumn()) {
                continue;
            }
            if (at.getName().equals("id")){
            	continue;
            }
            if(!domain.isNeedUpdate(at.getName())){
            	continue;
            }

            Serializable value=processValue((Serializable) BeanUtil.getSimpleProperty(domain,at.getName()));
            if(value instanceof String){
                value=HtmlUtil.processScript((String)value);
            }
			sb.append("`");
            sb.append(at.getName());
            sb.append("`=");
            sb.append(value);
            sb.append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append(" where id=");
        sb.append(processValue((Serializable) BeanUtil.getValue("id", domain)));
		return sb.toString();
	}




    @Override
	   public BatchSqlHolder getBatchAddSql(String tableName,List<? extends Domain> datas){
		//something like "insert into book(name,pbYear) values(?,?)"
	    //or like "insert into book(name,pbYear) values(x,x),(x,x),(x,x)" ps:this one is more efficient
		StringBuilder sb=new StringBuilder();
		sb.append("insert into ");
		sb.append(tableName);
		sb.append("(");
		List<Attribute> attributes=OrmContext.getAttributes(tableName);
		for(Attribute at:attributes){
			if(!at.isDbColumn()){
				continue;
			}
			sb.append("`");
			sb.append(at.getName());
			sb.append("`,");
		}
		sb.deleteCharAt(sb.length()-1);
		sb.append(") values");


		if(DbConfig.getDatabaseType()==DbConfig.DB_TYPE_ORACLE){
		    sb.append("(");
	        for(Attribute at:attributes){
	            if(!at.isDbColumn()){
	                continue;
	            }
	            sb.append("?,");
	        }
	        sb.deleteCharAt(sb.length()-1);
	        sb.append(")");
	        String sql = sb.toString();
	        List<BatchSetter> batchSetters=new ArrayList<BatchSetter>();
	        for(int i=0;i<datas.size();i++){
	            batchSetters.add(new BatchSetter(datas.get(i),attributes));
	        }
	        return new BatchSqlHolder(sql,batchSetters,false);
		}

		//mysql5.0之后支持了prepareStatement 但是发现，mysql下 使用prepareStatement执行时间是statement的8倍！！！

		else if(DbConfig.getDatabaseType()==DbConfig.DB_TYPE_MYSQL){
		    String sql_prefix=sb.toString();
		    boolean justRight=false;
		    List<String> sqls=new ArrayList<String>();
		    for(int i=0;i<datas.size();i++){
		        sb.append("(");
		        justRight=false;
		        Object data=datas.get(i);
		        for(Attribute at:attributes){
	                if(!at.isDbColumn()){
	                    continue;
	                }
	                Serializable value=processValue((Serializable) BeanUtil.getSimpleProperty(data, at.getName()));
	                if(value instanceof String){
	                    value=HtmlUtil.processScript((String)value);
	                }
	                sb.append(value);
	                sb.append(",");
	            }
		          sb.deleteCharAt(sb.length()-1);
		            sb.append("),");
		         if((i+1)%500==0){
		             justRight=true;
		             sb.deleteCharAt(sb.length()-1);
		             sqls.add(sb.toString());
		             sb.setLength(0);
		             sb.append(sql_prefix);
		         }
		    }
		    if(!justRight){
		        sb.deleteCharAt(sb.length()-1);
		        sqls.add(sb.toString());
		    }
		    return new BatchSqlHolder(sqls,true);
		}
		return null;
	}


	@Override
	public String getSelectPagerSql(Conditions conditions, Pager pager) {
		if(conditions.sql!=null)return conditions.sql+" limit "+pager.getOffset()+","+pager.getPageSize();
		//something like "select * from book where id=1 limit 0,1"
		return getSelectHql(conditions)+" limit "+pager.getOffset()+","+pager.getPageSize();
	}

	@Override
	public String getMulColumnPagerSql(Conditions conditions, String[] columns,Pager pager) {
		if(conditions.sql!=null)return conditions.sql+" limit "+pager.getOffset()+","+pager.getPageSize();
		//something like "select id,name from book where id=1 limit 0,1"
		return getMulColumnHql(conditions, columns)+" limit "+pager.getOffset()+","+pager.getPageSize();
	}


	@Override
	public String getColumnPagerSql(Conditions conditions, String attribute,
			Pager pager) {
		if(conditions.sql!=null)return conditions.sql+" limit "+pager.getOffset()+","+pager.getPageSize();
		return  getColumnHql(conditions, attribute)+" limit "+pager.getOffset()+","+pager.getPageSize();
	}


	@Override
	public String getMaxDomainHql(Conditions conditions, String attribute) {
		if(conditions.sql!=null)return conditions.sql;
		return conditions.getSelect().concat("* ").concat(super.getMaxDomainHql(conditions, attribute));
	}


	@Override
	public String getMulColumnHql(Conditions conditions, String[] columns) {
		if(columns==null||columns.length==0){
			throw new RuntimeException("columns信息不能为空");
		}
		if (conditions.sql != null) {
			return conditions.sql;
		}
		conditions.link = null;

		StringBuilder sb = new StringBuilder();
		sb.append(conditions.getSelect());
		for(String column:columns){
			sb.append(column);
			sb.append(",");
		}
		sb.deleteCharAt(sb.length()-1);
		sb.append(" from ");
		sb.append(conditions.getTableName());

		if (conditions.cs.size() == 0) {
			return sb.toString();
		}else{
	        sb.append(" ");
	        sb.append(conditions.getJoin());
			sb.append(" where ");
			return parse(conditions,sb);
		}
	}


	@Override
	public BatchSqlHolder getBatchUpdateSql(String tableName, List<? extends Domain> datas) {
		return getBatchUpdateSql(tableName, null, datas);
	}


	private void processDuplicate(Domain domain, StringBuilder sb,
			List<Attribute> attributes, List<String> sqls,Set<String> set) {
		sb.deleteCharAt(sb.length()-1);
		sb.append(" on duplicate key update ");


		for(Attribute at:attributes){
			if (!at.isDbColumn()) {
		        continue;
		    }
		    if (at.getName().equals("id")){
		    	continue;
		    }
		    if(set!=null){
	            if(!set.contains(at.getName())){
	            	continue;
	            }
		    }
			sb.append("`");
		    sb.append(at.getName());
			sb.append("`");
			sb.append("=values(`");
		    sb.append(at.getName());
		    sb.append("`),");
		}
          sb.deleteCharAt(sb.length()-1);
          sqls.add(sb.toString());
	}
	@Override
	public BatchSqlHolder getBatchUpdateSql(String tableName,String[] cols,List<? extends Domain> datas){
		Set<String> set = null;
		if(cols!=null){
			set = new HashSet<String>();
			for(String col:cols){
				set.add(col);
			}
		}
		Domain domain = datas.get(0);
		//something like "insert into book (id,name) values (x,x),(x,x),(x,x) on duplicate key update name=values(name);"
		StringBuilder sb=new StringBuilder();
		sb.append("insert into ");
		sb.append(tableName);
		sb.append("(");
		List<Attribute> attributes=OrmContext.getAttributes(tableName);
		for(Attribute at:attributes){
			if (!at.isDbColumn()) {
                continue;
            }
            if(set!=null){
            	if(!set.contains(at.getName())&&!at.getName().equals("id")){
            		continue;
            	}
            }
			sb.append("`");
			sb.append(at.getName());
			sb.append("`,");
		}
		sb.deleteCharAt(sb.length()-1);

		sb.append(") values");
		    String sql_prefix=sb.toString();
		    boolean justRight=false;
		    List<String> sqls=new ArrayList<String>();
		    for(int i=0;i<datas.size();i++){
		        sb.append("(");
		        justRight=false;
		        Object data=datas.get(i);
		        for(Attribute at:attributes){
		        	if (!at.isDbColumn()) {
		                continue;
		            }
		            if(set!=null){
		            	if(!set.contains(at.getName())&&!at.getName().equals("id")){
		            		continue;
		            	}
		            }
	                Serializable value=processValue((Serializable) BeanUtil.getSimpleProperty(data, at.getName()));
	                if(value instanceof String){
	                    value=HtmlUtil.processScript((String)value);
	                }
	                sb.append(value);
	                sb.append(",");
	            }
		        sb.deleteCharAt(sb.length()-1);
		        sb.append("),");
		         if((i+1)%500==0){
		             justRight=true;
		             processDuplicate(domain, sb, attributes, sqls,set);
		             sb.setLength(0);
		             sb.append(sql_prefix);
		         }
		    }
		    if(!justRight){
		        processDuplicate(domain, sb, attributes, sqls,set);
		    }
		    return new BatchSqlHolder(sqls,true);

	}

	@Override
	public String getGroupConcatSql(Conditions conditions, String attribute,
			String separator) {
		//something like "select group_concat(id separator ',') from TbUser where age>18 "
		if (conditions.sql != null) {
			return conditions.sql;
		}
		conditions.link = null;
		if (conditions.cs.size() == 0) {
			return new StringLink(conditions.getSelect(),"group_concat(" , attribute , " separator '"+separator+"') from " , conditions.getTableName()).toString();
		}
		StringBuilder sb = new StringBuilder();
		sb.append(conditions.getSelect());
		sb.append("group_concat(");
		sb.append(attribute);
		sb.append(" separator '");
		sb.append(separator);
		sb.append("') from ");
		sb.append(conditions.getTableName());
        sb.append(" ");
        sb.append(conditions.getJoin());
		sb.append(" where ");

		String str = parse(conditions,sb);
		return str;
	}

    public String getDistinctHqlPagerSql(Conditions conditions,String attribute, Pager pager) {
        if(conditions.sql!=null){
            return conditions.sql+" limit "+pager.getOffset()+","+pager.getPageSize();
        }
        return getDistinctHql(conditions, attribute)+" limit "+pager.getOffset()+","+pager.getPageSize();
    }
}
